package com.woniuxy.lab.woniu_lab.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.woniuxy.lab.woniu_lab.exception.*;
import com.woniuxy.lab.woniu_lab.model.*;
import com.woniuxy.lab.woniu_lab.dao.ExperDao;
import com.woniuxy.lab.woniu_lab.service.*;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.woniuxy.lab.woniu_lab.service.dto.ExperDto;
import com.woniuxy.lab.woniu_lab.service.dto.experDto.MyExperDto;
import com.woniuxy.lab.woniu_lab.util.DockerUtil;
import com.woniuxy.lab.woniu_lab.util.JwtUtil;
import com.woniuxy.lab.woniu_lab.util.ShellUtil;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;

import static com.woniuxy.lab.woniu_lab.exception.WebExceptionCode.*;

/**
 * <p>
 * 实验表 服务实现类
 * </p>
 *
 * @author Silas
 * @since 2023-03-15
 */
@Service
public class ExperServiceImpl extends ServiceImpl<ExperDao, Exper> implements ExperService {

    public static final String DISABLE = "CLOSE";
    public static final String RUNNING = "RUNNING";
    public static final String HOST = "43.136.98.253";
    public static final String ROOT = "root";
    public static final String MNT_LOCAL_SHARE_RUNNING = "/mnt/local_share/running/range";
    public static final String PASSWORD = "woniuCQ2023!@#$%^&*()";
    public static final String DOCKER = "DOCKER";
    public static final String ZIP = "zip";
    public static final String OTHER = "other";
    public static final String SUCCESS = "SUCCESS";
    @Resource
    private UserService userService;

    @Resource
    private ExperDao experDao;

    @Resource
    private RangeService rangeService;

    @Resource
    private PortService portService;

    @Resource
    private LogService logService;



    @Value("${dockerHttp}")
    private String dockerIp;
    @Value("${dockerPort}")
    private String dockerPort;


    @Value("${linux.ip}")
    private String linuxIp;
    @Value("${linux.username}")
    private String username;
    @Value("${linux.password}")
    private String password;
    @Override
    public IPage<MyExperDto> getMyCTF(IPage<Exper> iPage) {
        return getMyExperDtoIPage(iPage,"CTF");
    }

    @Override
    public IPage<MyExperDto> getMyExper(IPage<Exper> iPage) {
        return getMyExperDtoIPage(iPage,"EXPER");
    }

    @Override
    public ExperDto runningOrNot() {
        Integer userId = JwtUtil.getUserIdFromReq();
        List<Exper> list = list(new QueryWrapper<Exper>().eq("user_id", userId));
        for (Exper exper : list) {
            //有正在运行的环境
            if (RUNNING.equals(exper.getStatus()) && DOCKER.equals(exper.getFileOrDocker())){
                //返回正在运行的环境
              return BeanUtil.copyProperties(exper, ExperDto.class);
            }
        }
        //无正在运行的环境，返回null
        return null;
    }

    @Override
    public Boolean checkFlag(String flag ,Integer rangeId) {
        User user = userService.assertUser();
        Range range = rangeService.getById(rangeId);
        Exper exper = getOne(new QueryWrapper<Exper>()
                .eq("user_id", user.getId())
                .eq("status", "RUNNING")
                .eq("file_or_docker", "DOCKER")
        );
        List<Exper> files = list(new QueryWrapper<Exper>()
                .eq("user_id", user.getId())
                .eq("file_or_docker", "FILE").eq("result", null)
        );
        //判断用户是否拥有正在运行的实验
        if (exper==null && (files==null || files.size()==0)){
            throw new ExperException(EXPER_IS_NOT_RUNNING);
        }
        //判断传入靶场是否正常
        if (range==null){
            throw new RangeException(RANGE_ID_NOT_EXISTS);
        }
        boolean bl=false;
        //docker类型实验提交
        if(exper!=null){
            //判断实验是否为用户自身开启
            if (!user.getId().equals(exper.getUserId())){
                throw new ExperException(EXPER_IS_NOT_LIVE);
            }
            if (!Objects.equals(exper.getRangeId(), rangeId)){
                throw new WebException(9001,"你只能在启动的靶场页面,提交对应的flag");
            }
            if(exper.getFlag()==null){
                throw new ExperException(FLAG_IS_NOT_LIVE);
            }
            bl = exper.getFlag().equals(flag);
            if (bl){
                //避免重复提交
                if (exper.getResult()!=null){
                    throw new ExperException(DO_NOT_REPEAT_SUBMISSION_FLAG);
                }
                //验证通过，为用户添加积分。为靶场添加通过次数
                addScoreAndPassCount(range, user);

                if (range.getFileType().equals("docker")){
                    extracted("SUCCESS",exper);
                }else {
                    closeDockerCompose("SUCCESS");
                }
            }else {
                if (range.getFileType().equals("docker")){
                    extracted("FAIL",exper);
                }else {
                    closeDockerCompose("FAIL");
                }
            }
        //file类型实验提交
        }else if(files!=null && files.size()!=0) {

            Exper file = getOne(new QueryWrapper<Exper>()
                    .eq("user_id", user.getId())
                    .eq("file_or_docker", "FILE")
                    .eq("range_id", rangeId)
            );
            if (ObjectUtil.isEmpty(file)){
                throw new ExperException(EXPER_IS_NOT_RUNNING);
//                if (!Objects.equals(file.getRangeId(), rangeId)){
//                    throw new WebException(9001,"你只能在启动的靶场页面,提交对应的flag");
//                }
            }
            //判断实验是否为用户自身开启
            if (!user.getId().equals(file.getUserId())){
                throw new ExperException(EXPER_IS_NOT_LIVE);
            }
            if(file.getFlag()==null){
                throw new ExperException(FLAG_IS_NOT_LIVE);
            }
            bl = file.getFlag().equals(flag);
            if (bl) {
                //避免重复提交
                if (SUCCESS.equals(file.getResult())){
                    throw new ExperException(DO_NOT_REPEAT_SUBMISSION_FLAG);
                }
                closeFile(file,SUCCESS,"CLOSE");
                //验证通过，为用户添加积分。为靶场添加通过次数
                addScoreAndPassCount(range, user);
            }else{
                closeFile(file,"FAIL","CLOSE");
            }
        }
        return bl;
    }

    private void addScoreAndPassCount(Range range,User user) {
        //添加通过次数
        range.setPassCount(range.getPassCount()+1);
        rangeService.updateById(range);
        //为用户添加积分
        if(range.getCtfOrExper().equals("CTF")){
            user.setCtfScore(user.getCtfScore()+1);
        }else {
            user.setRangeScore(user.getRangeScore()+1);
        }
        userService.updateById(user);
    }

    private void closeFile(Exper file,String result,String status) {
        file.setResult(result);
        file.setReleaseTime(LocalDateTime.now());
        file.setStatus(status);
        updateById(file);
    }

    private void extracted(String result,Exper exper) {

        exper.setReleaseTime(LocalDateTime.now());
        exper.setStatus("CLOSE");
        //不管是否回答正确,都关闭实验
        DockerUtil dockerUtil = DockerUtil.getInstance(dockerIp,dockerPort);
        System.out.println(exper.getContainerId());
        boolean containerState = dockerUtil.getContainerState(exper.getContainerId());
        if (containerState) dockerUtil.stopContainer(exper.getContainerId());
        //关闭端口
        ShellUtil shell = ShellUtil.getShell(linuxIp, username, password);
        shell.closePort(exper.getPortOuter());
        exper.setResult(result);
        updateById(exper);
    }

    @Override
    public void release(Integer userId) {
        Exper exper = null;
        try {
            exper = this.getOne(new QueryWrapper<Exper>()
                    .eq("user_id", userId)
                    .eq("file_or_docker", "DOCKER")
                    .eq("status", "RUNNING"));
        } catch (Exception e) {
            e.printStackTrace();
            throw new WebException(DOCKER_RELEASE_FAIL);

        }
        if(exper != null){
            Range range = rangeService.getById(exper.getRangeId());
            if (range.getFileType().equals("docker")){
                closeDocker(exper,userId);
            }else{
                closeDockerCompose("FAIL");
            }
        }else {
            throw new WebException(CONTAINER_NOTHINGNESS);
        }
    }

    private void closeDocker(Exper exper,Integer userId) {
        exper.setStatus("CLOSE");
        exper.setResult("FAIL");
        exper.setReleaseTime(LocalDateTime.now());
        List<Port> ports = portService.list(new QueryWrapper<Port>().eq("user_id", userId));
        //删除端口
        for (Port port : ports) {
            portService.removeById(port.getId());
        }
        DockerUtil dockerUtil = DockerUtil.getInstance(dockerIp,dockerPort);
        boolean containerState = dockerUtil.getContainerState(exper.getContainerId());
        if (containerState) dockerUtil.stopContainer(exper.getContainerId());


        //关闭端口
        ShellUtil shell = ShellUtil.getShell(linuxIp, username, password);
        shell.closePort(exper.getPortOuter());
        this.updateById(exper);
    }

    @Override
    public void delay(User user, Integer rangeId) {
        Range range = rangeService.getById(rangeId);
        if (OTHER.equals(range.getFileType())){
            throw new ExperException(FILE_NOT_NEED_DELAY);

        }
        Exper exper = getOne(new QueryWrapper<Exper>()
                .eq("user_id", user.getId())
                .eq("status", "RUNNING")
                .eq("file_or_docker", "DOCKER"));
        if(exper==null){
            throw new WebException(DOCKER_NOT_RUNNING);
        }
        exper.setReleaseTime(exper.getReleaseTime().plusMinutes(30));
        updateById(exper);
    }

    private IPage<MyExperDto> getMyExperDtoIPage(IPage<Exper> iPage,String exper) {
        //判断用户

        User user = userService.assertUser();
        return experDao.getMyExperDtoIPage(iPage,user.getId(),exper);
    }

    /**
     * 关闭dockerCompose
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void closeDockerCompose(String result) {

        User user = userService.assertUser();
        Integer userId = user.getId();
        //校验端口
        List<Port> ports =  portService.list(new QueryWrapper<Port>().eq("user_id", userId));
        if (ports==null||ports.size()==0){
            throw new PortException(PORT_NOT_EXISTS);
        }

        //判断实验是否存在
        Exper exper = getOne(new QueryWrapper<Exper>()
                .eq("user_id", userId)
                .eq("status", RUNNING)
                .eq("file_or_docker", "DOCKER")
        );
        if (ObjectUtil.isEmpty(exper)) {
            throw new ExperException(EXPER_NOT_EXIST);
        }
        Range range = rangeService.getById(exper.getRangeId());

        ShellUtil shell = ShellUtil.getShell(HOST, ROOT, PASSWORD);
        //关闭compose
        shell.stopCompose(MNT_LOCAL_SHARE_RUNNING+userId);
        //删除用户文件
        shell.delUserFolder(MNT_LOCAL_SHARE_RUNNING+userId);
        if (range.getFileType().equals(ZIP)){
            //删除用户镜像
            shell.delUserImage("range"+userId+"-web:latest");
        }


        //修改实验记录
        exper.setStatus(DISABLE);
        //修改实验结果
        exper.setResult(result);
        updateById(exper);
        //禁用端口
        for (Port port : ports) {
            shell.closePort(port.getPort());
}
        //删除用户占用的端口
        LambdaQueryWrapper<Port> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Port::getUserId, userId);
        boolean rows = portService.remove(wrapper);
        //异步写入日志
        logService.greatLog(user,range,"关闭"+range.getName()+"("+range.getId()+")"+"实验");
    }
}
